/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package wiiscript;

import java.awt.Color;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;

/**
 *
 * @author Evan
 */
public class ColorCodeDoc extends DefaultStyledDocument {
    private StyleContext sc = new StyleContext();
    private DefaultStyledDocument doc;
    private Element rootElement;
    private Frame frame;

    private Style defultStyle = sc.addStyle("DefultStyle", null);
    private Style debugStyle = sc.addStyle("DebugStyle", defultStyle);
    private Style numberStyle = sc.addStyle("NumberStyle", defultStyle);
    private Style braceStyle = sc.addStyle(("BraceStyle"), debugStyle);
    private Style operatorStyle = sc.addStyle("OperatorStyle", defultStyle);
    private Style equalStyle = sc.addStyle("EqualStyle", defultStyle);
    private Style commentStyle = sc.addStyle("CommentStyle", defultStyle);
    private Style stringStyle = sc.addStyle("StringStyle", defultStyle);
    private Style keywordStyle = sc.addStyle("KeywordStyle", defultStyle);

    private ArrayList<Word> colorCodeList = new ArrayList<Word>();
    private String[] keywords = {"if", "then", "end", "endif", "else", "elseif",
                                 "begin", "while", "endwhile", "do", "for", "to",
                                 "downto", "step", "wait"};
    private String[] comments = {"//", "'", "REM", "%"};
    private String[] strings = {"\"(.)*\"", "\'(.)*\'"};
    private String[] equals = {":=", "="};
    private String[] numbers = {"\\d+(\\.\\d+)?", "\\.\\d+", "\\d+(,\\d{3,})+"};
    private String[] braces = {"(", ")", "[", "]", "{", "}"};
    private String[] operators = {"\\+", "-", "/", "\\*", "plus", "minus", "div",
                                  "times", "mod", "\\^", "==", "<", ">", "<=",
                                  ">=", "!=", "<>"};

    private Pattern debugPattern = Pattern.compile("debug\\D");
    private Pattern[] equalPatterns;
    private Pattern[] numberPatterns;
    private Pattern[] bracePatterns;
    private Pattern[] operatorPatterns;
    private Pattern[] commentPatterns;
    private Pattern[] stringPatterns;
    private Pattern[] keywordPatterns;
    private Pattern multiCommentPattern = Pattern.compile("/\\*(.|\\s)*(\\*/)");

    public ColorCodeDoc(Frame frame) {
        doc = this;

        defultStyle.addAttribute(StyleConstants.Foreground, Color.black);

        keywordStyle.addAttribute(StyleConstants.Foreground, Color.blue);
        keywordStyle.addAttribute(StyleConstants.Bold, true);
        
        debugStyle.addAttribute(StyleConstants.Foreground, Color.blue);

        numberStyle.addAttribute(StyleConstants.Foreground, new Color(34, 139, 34));

        braceStyle.addAttribute(StyleConstants.Foreground, Color.cyan);

        equalStyle.addAttribute(StyleConstants.Foreground, new Color(0, 128, 128));

        operatorStyle.addAttribute(StyleConstants.Foreground, new Color(128, 0, 128));

        commentStyle.addAttribute(StyleConstants.Foreground, Color.darkGray);
        commentStyle.addAttribute(StyleConstants.Italic, true);

        stringStyle.addAttribute(StyleConstants.Foreground, Color.blue);

        //Set tap size
        TabStop[] tabs = new TabStop[15];
        for(int i = 0; i < tabs.length; i++) {
            tabs[i] = new TabStop(20 * (i+1), TabStop.ALIGN_LEFT, TabStop.LEAD_NONE);
        }
        TabSet tabset = new TabSet(tabs);
        AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.TabSet, tabset);
        setParagraphAttributes(0, getLength(), aset, false);

        //Set comment patterns
        commentPatterns = new Pattern[comments.length];
        for(int i = 0; i < comments.length; i++) {
            commentPatterns[i] = Pattern.compile(comments[i] + "(.)*\\n");
        }
        //Set equal patterns
        equalPatterns = new Pattern[equals.length];
        for(int i = 0; i < equals.length; i++) {
            equalPatterns[i] = Pattern.compile(equals[i]);
        }
        //Set number patterns
        numberPatterns = new Pattern[numbers.length];
        for(int i = 0; i < numbers.length; i++) {
            numberPatterns[i] = Pattern.compile("\\b" + numbers[i]);
        }
        //Set brace patterns
        bracePatterns = new Pattern[braces.length];
        for(int i = 0; i < braces.length; i++) {
            bracePatterns[i] = Pattern.compile("\\" + braces[i]);
        }
        //Set operatorPatterns
        operatorPatterns = new Pattern[operators.length];
        for(int i = 0; i < operators.length; i++) {
            operatorPatterns[i] = Pattern.compile(operators[i]);
        }
        //Set keywordPatterns
        keywordPatterns = new Pattern[keywords.length];
        for(int i = 0; i < keywords.length; i++) {
            keywordPatterns[i] = Pattern.compile("\\b" + keywords[i] + "\\b");
        }
        //Set stringPatterns
        stringPatterns = new Pattern[strings.length];
        for(int i = 0; i < strings.length; i++) {
            stringPatterns[i] = Pattern.compile(strings[i]);
        }
        rootElement = doc.getDefaultRootElement();

        this.frame = frame;
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
        super.insertString(offs, str, a);
        colorCode(offs, str.length());
        if(str.equals("\n")) {
            insertTabs(offs);
        }
        String text = getText(0, getLength());
        for(int i = 0; i < Frame.connectionList.size(); i++) {
            String s = (String)Frame.connectionList.get(i);
            if(!text.contains(s)) {
                Frame.connectionList.remove(i);
            } else {
                text = text.replaceFirst(s, "");
            }
        }
        Frame.saved = false;

        if(Main.getOS() == Main.OS.Mac) {
            frame.getRootPane().putClientProperty("Window.documentModified", "true");
        }
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
        super.remove(offs, len);
        colorCode(offs, len);
    }

    private void insertTabs(int offs) throws BadLocationException {
        int line = rootElement.getElementIndex(offs);
        int lineOffset = rootElement.getElement(line).getStartOffset();
        int lineEnd = rootElement.getElement(line).getEndOffset();
        String text = getText(lineOffset, lineEnd - lineOffset);
        int newlineIndex = text.indexOf("\n");
        if(newlineIndex > 0) {
            text = text.substring(0, text.indexOf("\n"));
        }
        int tabCount = 0;
        char[] textArray = text.toCharArray();
        for(char c : textArray) {
            if(c == '\t') {
                tabCount++;
            }
        }
        int nextLine = rootElement.getElement(line + 1).getStartOffset();
        for(int i = 0; i < tabCount; i++) {
            this.insertString(nextLine, "\t", null);
        }
    }
    private void colorCode(int offset, int length) throws BadLocationException {
        String text = this.getText(0, this.getLength());
        text = text.toLowerCase();
        text += "\n";

        doc.setCharacterAttributes(0, text.length(), defultStyle, true);

        Matcher debugMatcher = debugPattern.matcher(text);
        while(debugMatcher.find()) {
            int start = debugMatcher.start();
            colorCodeList.add(new Word(start, "debug"));
        }
        for(Pattern p : keywordPatterns) {
            Matcher keywordMatcher = p.matcher(text);
            while(keywordMatcher.find()) {
                int start = keywordMatcher.start();
                int end = keywordMatcher.end();
                colorCodeList.add(new Word(start, "keyword", end - start));
            }
        }
        for(Pattern p : equalPatterns) {
            Matcher equalMatcher = p.matcher(text);
            while(equalMatcher.find()) {
                int start = equalMatcher.start();
                int end = equalMatcher.end();
                colorCodeList.add(new Word(start, "=", end - start));
            } 
        }
        for(Pattern p : bracePatterns) {
            Matcher braceMatcher = p.matcher(text);
            while(braceMatcher.find()) {
                int start = braceMatcher.start();
                int end = braceMatcher.end();
                colorCodeList.add(new Word(start, "brace", end - start));
            }
        }
        for(Pattern p : numberPatterns) {
            Matcher numberMatcher = p.matcher(text);
            while(numberMatcher.find()) {
                int start = numberMatcher.start();
                int end = numberMatcher.end();
                colorCodeList.add(new Word(start, "number", end - start));
            }
        }
        for(Pattern p : operatorPatterns) {
            Matcher operatorMatcher = p.matcher(text);
            while(operatorMatcher.find()) {
                int start = operatorMatcher.start();
                int end = operatorMatcher.end();
                colorCodeList.add(new Word(start, "operator", end - start));
            }
        }
        for(Pattern p : stringPatterns) {
            Matcher stringPMatcher = p.matcher(text);
            while(stringPMatcher.find()) {
                int start = stringPMatcher.start();
                int end = stringPMatcher.end();
                colorCodeList.add(new Word(start, "string", end - start));
            }
        }
        for(Pattern p : commentPatterns) {
            Matcher commentMatcher = p.matcher(text);
            while(commentMatcher.find()) {
                int start = commentMatcher.start();
                int end = commentMatcher.end();
                colorCodeList.add(new Word(start, "comment", end - start));
            }
        }
        Matcher multiCommentMatcher = multiCommentPattern.matcher(text);
        while(multiCommentMatcher.find()) {
            int start = multiCommentMatcher.start();
            int end = multiCommentMatcher.end();
            colorCodeList.add(new Word(start, "comment", end - start));
        }
        //Apply Styles
        for(Word i : colorCodeList) {
            String wordName = i.getWord();
            Style style = null;
            if(wordName.equals("debug")) {
                style = debugStyle;
            } else if(wordName.equals("number")) {
                style = numberStyle;
            } else if(wordName.equals("=")) {
                style = equalStyle;
            } else if(wordName.equals("comment")) {
                style = commentStyle;
            } else if(wordName.equals("keyword")) {
               style = keywordStyle;
            } else if(wordName.equals("brace")) {
                style = braceStyle;
            } else if(wordName.equals("operator")) {
                style = operatorStyle;
            } else if(wordName.equals("string")) {
                style = stringStyle;
            }
            if(style != null) {
                doc.setCharacterAttributes(i.getIndex(), i.getLength(), style, false);
            }
        }
        colorCodeList.clear();
    }

    class Word {
        private int index;
        private String word;
        private int length;
        public Word(int index, String word) {
            this.index = index;
            this.word = word;
            this.length = word.length();
        }
        public Word(int index, String word, int length) {
            this.index = index;
            this.word = word;
            this.length = length;
        }
        public int getIndex() {
            return index;
        }
        public int getLength() {
            return length;
        }
        public String getWord() {
            return word;
        }
    }
}
